package org.ucas.faker.web.fetcher.parse;

import javafx.util.Pair;
import org.ucas.faker.web.fetcher.annotation.Connection;
import org.ucas.faker.web.fetcher.annotation.JoinCond;
import org.ucas.faker.web.fetcher.cond.AndCond;
import org.ucas.faker.web.fetcher.cond.CompCond;
import org.ucas.faker.web.fetcher.cond.FetchCond;
import org.ucas.faker.web.fetcher.meta.ColumnField;
import org.ucas.faker.web.fetcher.meta.ConstantField;
import org.ucas.faker.web.fetcher.meta.Table;
import org.ucas.faker.web.utils.StringUtils;

import javax.persistence.Column;
import javax.persistence.Transient;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class TableParser {

  private final TableMeta target;

  private Table table;

  private List<Pair<Table, Connection>> conns = new ArrayList<>();

  public TableParser(TableMeta meta) {
    this.target = meta;
  }

  public TableParser() {
    this.target = new TableMeta();
  }

  public TableMeta parse(Class clazz) {
    parseTable(clazz);

    while (clazz != null) {
      for (Field field : clazz.getDeclaredFields())
        parseColumn(field);
      clazz = clazz.getSuperclass();
    }

    return target;
  }

  private void parseTable(Class clazz) {
    javax.persistence.Table annotation = (javax.persistence.Table) clazz.getAnnotation(javax.persistence.Table.class);
    table = new Table(annotation == null ? null : annotation.name());
    if (table.name == null || "".equals(table.name))
      table.name = StringUtils.camel2Underline(clazz.getSimpleName());
    table.clazz = clazz;
    target.tables.add(table);

    Connection conn = (Connection) clazz.getAnnotation(Connection.class);
    if (conn != null) conns.add(new Pair(table, conn));
  }

  private void parseColumn(Field field) {
    if (field.getAnnotation(Transient.class) != null)
      return;


    Column annotation = (Column) field.getAnnotation(Column.class);
    ColumnField column = new ColumnField();
    column.column = annotation == null ? null : annotation.name();
    if (column.column == null || "".equals(column.column))
      column.column = StringUtils.camel2Underline(field.getName());
    column.alias = field.getName();
    column.table = this.table;

    table.columns.add(column);
    String prefix = table == null || table.name == null ? "" : table.name + ".";
    target.fields.put(prefix + column.alias, column);
    if (!column.column.equals(column.alias)) target.fields.put(prefix + column.column, column);

    Connection conn = (Connection) field.getAnnotation(Connection.class);
    if (conn != null) conns.add(new Pair(table, conn));
  }


  private FetchCond buildCond(List<Pair<ColumnField, ColumnField>> constraints) {
    if (constraints == null || constraints.size() == 0) return null;
    if (constraints.size() == 1) {
      Pair<ColumnField, ColumnField> pair = constraints.get(0);
      return new CompCond(pair.getKey(), "=", pair.getValue());
    }

    return new AndCond(constraints);
  }

  public void buildGraph() {
    JoinGraph graph = target.graph;
    for (Pair<Table, Connection> pair : conns) {
      Connection conn = pair.getValue();
      Table left = pair.getKey();

      if (left == null || conn == null) continue;

      Table right = target.table(conn.table());
      if (right == null) continue;
      List<Pair<ColumnField, ColumnField>> constrainsts = new ArrayList<>();
      for (JoinCond cond : conn.conds()) {
        ColumnField leftField = target.column(left.name + "." + cond.column());
        ColumnField rightField = cond.costant() ? new ConstantField(cond.target()) : target.column(right.name + "." + cond.target());
        if (leftField.table == null || rightField.table == null) continue;
        constrainsts.add(new Pair(leftField, rightField));
      }
      graph.connect(left, right, buildCond(constrainsts));
    }

  }
}
